/**!
 * libsha1 - provides an easy way to generate sha1 hashes
 * Copyright (C) 2009 Sebastien Rannou
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef _BSD_SOURCE
#define _BSD_SOURCE
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

/**!
 * @author	rannou_s <rannou.sebastien@gmail.com>
 * This implementation of SHA1 uses the first method defined in the RFC3174
 * http://www.faqs.org/rfcs/rfc3174.html
 * each variables defined here follows the convention used in the
 * document of reference, so let's have a look at it, it's well explained.
 */

typedef unsigned long	ulong;
typedef unsigned int	uint;
typedef unsigned char	uchar;

typedef struct		sha1_s		/* sha1 structure */
{
  uint			w[80];		/* 16 + 64, used to compute */
  uint			h[5];		/* contains H0 H1 H2 H3 H4 H5*/
  uint			a[5];		/* contains A B C D E */
}			sha1_t;

#define	W(x)		sha1->w[x]
#define	H(x)		sha1->h[x]

#define	A		sha1->a[0]
#define	B		sha1->a[1]
#define	C		sha1->a[2]
#define	D		sha1->a[3]
#define	E		sha1->a[4]

/**!
 * Performs a circular left shift
 * S^n(X)  =  (X << n) OR (X >> 32-n)
 */

static  uint
sha1_op_left_shift(uint n, uint x)
{
  return (x << n) | (x >> (32 - n));
}

/**!
 * Performs the F function (returns a different bitwise according to t)
 */

static  uint
sha1_f(int t, sha1_t *sha1)
{
  if (t < 20)
    return (B & C) | ((~B) & D);
  if (t < 40)
    return B ^ C ^ D;
  if (t < 60)
    return (B & C) | (B & D) | (C & D);
  return B ^ C ^ D;
}

/**!
 * Performs the K function (returns a different constant according to t)
 */

static  uint
sha1_k(int t)
{
  if (t < 20)
    return 0x5A827999;
  if (t < 40)
    return 0x6ED9EBA1;
  if (t < 60)
    return 0x8F1BBCDC;
  return 0xCA62C1D6;
}

/**!
 * Computes one `n block` of 16 words
 * assuming strlen(input) is equal to 64 and padded
 */

static  void
sha1_compute_block(uchar *input, sha1_t *sha1)
{
  int			i, t;
  uint			tmp;

  for (t = 0; t < 16; t++)
    {
      W(t) = input[t * 4] << 24;
      W(t) |= input[t * 4 + 1] << 16;
      W(t) |= input[t * 4 + 2] << 8;
      W(t) |= input[t * 4 + 3];
    }
  for (t = 16; t < 80; t++)
    {
      W(t) = sha1_op_left_shift(1, W(t - 3) ^ W(t - 8) ^ W(t - 14) ^ W(t - 16));
    }
  for (i = 0; i < 5; i++)
    {
      sha1->a[i] = sha1->h[i];
    }
  for (t = 0; t < 80; t++)
    {
      tmp = sha1_op_left_shift(5, A) + sha1_f(t, sha1) + E + W(t) + sha1_k(t);
      E = D;
      D = C;
      C = sha1_op_left_shift(30, B);
      B = A;
      A = tmp;
    }
  for (i = 0; i < 5; i ++)
    {
      sha1->h[i] += sha1->a[i];
    }
}

/**!
 * Let's initialize sha1 structure with some constants
 */

static  void
sha1_initialize(sha1_t *sha1)
{
  sha1->h[0] = 0x67452301;
  sha1->h[1] = 0xEFCDAB89;
  sha1->h[2] = 0x98BADCFE;
  sha1->h[3] = 0x10325476;
  sha1->h[4] = 0xC3D2E1F0;
}

/**!
 * Let's copy the small-len at the end of the word
 */

static  void
sha1_set_len(char *word, int len)
{
  uint			ulen = len & 0xFFFFFFFF;

  word[60] = ulen >> 24;
  word[61] = ulen >> 16;
  word[62] = ulen >> 8;
  word[63] = ulen;
}

/**!
 * Let's return the hash according to the sha1 structure
 */

static  char *
sha1_get_hash(sha1_t *sha1)
{
  char			result[140 + 1];

  snprintf(result, 140, "%08x%08x%08x%08x%08x", H(0), H(1), H(2), H(3), H(4));
  return (strdup(result));
}

/**!
 * Returns an allocated string representing the hash of the string
 * It assumes that the len of the string is less than 2^32 bytes
 * If it's not the case, the way we store the len in the last block is incorrect
 */

char *
sha1_string(char *input)
{
  sha1_t		sha1;
  char			word[64];
  int			i, len, computed, flag;

  if (input == NULL)
    return (NULL);
  sha1_initialize(&sha1);
  len = strlen(input);
  flag = 0;
  for (i = 0; i <= len; i += 64)
    {
      computed = i + 64 > len ? len % 64 : 64;
      strncpy(word, &input[i], computed);
      if (computed != 64)
	{
	  memset(&word[computed], 0, 64 - computed);
	  word[computed] = (char) 0x80;
	  if (computed < 56)
	    sha1_set_len(word, len * 8);
	  else	    
	    ++flag;
	}
      sha1_compute_block((uchar *) word, &sha1);
    }
  if (flag > 0)
    {
      memset(word, 0, 64);
      sha1_set_len(word, len * 8);
      sha1_compute_block((uchar *) word, &sha1);
    }
  return (sha1_get_hash(&sha1));
}

/**!
 * Returns a hash from a file descriptor
 * Works the same way as sha1_string
 */

static char *
sha1_fd(int fd)
{
  sha1_t		sha1;
  char			word[64];
  int			len, computed, flag;

  sha1_initialize(&sha1);  
  flag = len = 0;
  computed = 64;
  while (computed == 64)
    {
      computed = read(fd, word, 64);
      len += computed;
      if (computed != 64)
	{
	  memset(&word[computed], 0, 64 - computed);
	  word[computed] = (char) 0x80;
	  if (computed < 56)
	    sha1_set_len(word, len * 8);
	  else	    
	    ++flag;
	}
      sha1_compute_block((uchar *) word, &sha1);
    }
  if (flag > 0)
    {
      memset(word, 0, 64);
      sha1_set_len(word, len * 8);
      sha1_compute_block((uchar *) word, &sha1);
    }
  return (sha1_get_hash(&sha1));  
}

/**!
 * Returns an allocated string representing the hash of the file
 * pointed by path
 */

char *
sha1_file(char *path)
{
  int			fd;
  char			*result;

  if ((fd = open(path, O_RDONLY)) == -1)
    return (NULL);
  result = sha1_fd(fd);
  close(fd);
  return (result);
}
